﻿using System;
using System.Collections.Generic;
using Unity.Netcode;
using Unity.Sync.Relay;
using Unity.Sync.Relay.Lobby;
using Unity.Sync.Relay.Model;
using Unity.Sync.Relay.Transport.Netcode;
using UnityEngine;

namespace GameInit.Framework
{
    public class UosSyncRelay : MonoBehaviour
    {
        public static UosSyncRelay Instance => _instance;
        private static UosSyncRelay _instance;

        /// <summary>
        /// 玩家名称
        /// </summary>
        public string PlayerName;
        
        /// <summary>
        /// 玩家id
        /// </summary>
        public string Uid {get; private set; }
        
        /// <summary>
        /// 房间命名空间
        /// </summary>
        public string RoomNameSpace = "UnityGame";
        
        /// <summary>
        /// 玩家信息
        /// </summary>
        public Dictionary<string,RelayPlayer> PlayerDic = new Dictionary<string, RelayPlayer>();
        
        /// <summary>
        /// 加入的房间
        /// </summary>
        private RelayRoom _joinRelayRoom;

        /// <summary>
        /// 是否初始化
        /// </summary>
        private bool _isInit;
        
        private void Awake()
        {
            if (_instance == null)
                _instance = this;
        }

        private void Update()
        {
            if (NetworkManager.Singleton != null && _isInit == false)
                Init();
        }

        /// <summary>
        /// 初始化
        /// </summary>
        private void Init()
        {
            // 标记初始化
            _isInit = true;
            
            // 设置玩家数据
            SetPlayerData(PlayerName);
                
            //需要在创建或者加入房间之前，配置好回调函数
            var callbacks = new RelayCallbacks();
            // ------ 设置回调事件 ------
            callbacks.RegisterConnectToRelayServer(OnConnectToRelayServer);//当连接到 Relay 服务器
            callbacks.RegisterPlayerEnterRoom(OnPlayerEnterRoom);//当有玩家加入房间
            callbacks.RegisterPlayerLeaveRoom(OnPlayerLeaveRoom);//当有玩家离开房间
            callbacks.RegisterMasterClientMigrate(OnMasterClientMigrate);//在退出房间或掉线时会触发
            callbacks.RegisterSetHeartbeat(OnSetHeartBeat);//设置心跳超时时间
            callbacks.RegisterRoomInfoUpdate(OnRoomInfoUpdate); // 房间信息跟新
            callbacks.RegisterPlayerInfoUpdate(OnPlayerInfoUpdate);
                
            NetworkManager.Singleton.GetComponent<RelayTransportNetcode>().SetCallbacks(callbacks);
        }
        
        /// <summary>
        /// 设置玩家信息
        /// </summary>
        public void SetPlayerData(string playerName)
        {
            //需要在创建或者加入房间之前，设置好玩家信息
            Uid = Guid.NewGuid().ToString();
            // log
            Debug.Log("Uid "+Uid);
            // relay
            var relayTransport = NetworkManager.Singleton.GetComponent<RelayTransportNetcode>();
            // 设置玩家数据
            relayTransport.SetPlayerData(Uid,playerName,new Dictionary<string, string>()
            {
                {"IsReady","false"},
            });
        }


        #region 回调事件
        /// <summary>
        /// 当连接到 Relay 服务器
        /// </summary>
        /// <param name="code"></param>
        /// <param name="room"></param>
        private void OnConnectToRelayServer(uint code, RelayRoom room)
        {
            Debug.Log("OnConnectToRelay 服务器已调用");
            
            if (code == (uint)RelayCode.OK)
            {
                Debug.Log($"连接到中继服务器成功。（房间：{ room.Name}）");
            }
            else
            {
                Debug.LogError("连接到中继服务器失败，代码为{code}。");
            }
            
            // 清空
            PlayerDic.Clear();
            
            // 记录玩家信息
            foreach (var player in room.Players)
                PlayerDic.Add(player.Value.ID, player.Value);
            
            // log
            foreach (var player in PlayerDic)
            {
                Debug.Log($"{player.Key} - {player.Value.ID}");
            }
            
            // 记录房间
            _joinRelayRoom = room;
            
            // 事件 - 跟新房间信息事件
            SycnRalayUI.Instance.RefreshRoom(room,PlayerDic);
        }
        
        /// <summary>
        /// 房间信息更新
        /// </summary>
        /// <param name="room"></param>
        private void OnRoomInfoUpdate(RelayRoom room)
        {
            // log
            Debug.Log("房间信息更新");
            Debug.Log($"名称：{room.Name}（自定义属性数 {room.CustomProperties.Count}）");
            
            foreach (var item in room.CustomProperties)
            {
                Debug.Log($"{item.Key} - {item.Value}");
            }
            
            // 清空
            PlayerDic.Clear();
            
            // 记录玩家信息
            foreach (var player in room.Players)
                PlayerDic.Add(player.Value.ID, player.Value);

            // 记录房间
            _joinRelayRoom = room;
            // 事件 - 跟新房间信息事件
            SycnRalayUI.Instance.RefreshRoom(_joinRelayRoom,PlayerDic);
        }

        /// <summary>
        /// 玩家数据跟新
        /// </summary>
        private void OnPlayerInfoUpdate(RelayPlayer relayPlayer)
        {
            if(PlayerDic.ContainsKey(relayPlayer.ID))
                PlayerDic[relayPlayer.ID] = relayPlayer;
            else
            {
                // log
                Debug.LogError("玩家数据跟新失败 "+relayPlayer.ID);
                // log
                foreach (var player in PlayerDic)
                {
                    Debug.Log($"{player.Key} - {player.Value.ID}");
                }
            }

            // 事件 - 跟新房间信息事件
            SycnRalayUI.Instance.RefreshRoom(_joinRelayRoom,PlayerDic);
        }

        /// <summary>
        /// 客户端掉线
        /// </summary>
        /// <param name="newMasterClient"></param>
        private void OnMasterClientMigrate(uint newMasterClient)
        {
            Debug.Log("主客户端掉线");
            Debug.Log($"新主客户端 {newMasterClient}");
            
            // 跟新
            _joinRelayRoom.MasterClientID = newMasterClient;
            
            // 事件 - 跟新房间信息事件
            //EventManager.TriggerEvent("Event_UpdateLobby",_joinRelayRoom,PlayerDic);
        }
        
        /// <summary>
        /// 玩家进入房间
        /// </summary>
        /// <param name="player"></param>
        private void OnPlayerEnterRoom(RelayPlayer player)
        {
            Debug.Log("玩家进入房间");
            Debug.Log($"Player {player.TransportId} Enter Room");
            
            PlayerDic[player.ID] = player;

            // 事件 - 跟新房间信息事件
            SycnRalayUI.Instance.RefreshRoom(_joinRelayRoom,PlayerDic);
        }
        
        /// <summary>
        /// 玩家离开房间
        /// </summary>
        /// <param name="player"></param>
        private void OnPlayerLeaveRoom(RelayPlayer player)
        {
            Debug.Log("玩家离开房间");
            Debug.Log($"Player ({ player.TransportId} , {player.ID}) Leave Room");

            if (PlayerDic.ContainsKey(player.ID))
                PlayerDic.Remove(player.ID);
            else
            {
                // log
                Debug.LogError("玩家数据跟新失败 "+player.ID);
                // log
                foreach (var item in PlayerDic)
                {
                    Debug.Log($"{item.Key} - {item.Value.TransportId}");
                }
            }

            // 事件 - 跟新房间信息事件
            //EventManager.TriggerEvent("Event_UpdateLobby",_joinRelayRoom,PlayerDic);
        }
        
        /// <summary>
        /// 设置心跳超时时间回调
        /// </summary>
        /// <param name="code"></param>
        /// <param name="timeout"></param>
        private void OnSetHeartBeat(uint code, uint timeout)
        {
            Debug.Log("设置心跳超时时间回调");
            Debug.Log($"Code {code} Timeout {timeout}");
        }
        #endregion

        #region 功能
        /// <summary>
        /// 创建房间
        /// </summary>
        public void CreateRoom(string roomName, int maxPlayers,Action<bool> callback)
        {
            if (NetworkManager.Singleton && !NetworkManager.Singleton.IsListening)
            {
                StartCoroutine(LobbyService.AsyncCreateRoom(new CreateRoomRequest()
                {
                    Name = roomName,
                    Namespace = RoomNameSpace,
                    MaxPlayers = maxPlayers,
                    Visibility = LobbyRoomVisibility.Public,
                    OwnerId = Uid,
                    CustomProperties = new Dictionary<string, string>()
                    {
                        {"IsStartGame","false"},
                    },
                }, (resp) =>
                {
                    if (resp.Code == (uint)RelayCode.OK)
                    {
                        // log
                        Debug.Log("创建房间成功");
                            
                        if (resp.Status == LobbyRoomStatus.ServerAllocated)
                        {
                            // relay
                            var relayTransport = NetworkManager.Singleton.GetComponent<RelayTransportNetcode>();
                            // 设置房间数据
                            relayTransport.SetRoomData(resp);
                            // 回调
                            callback?.Invoke(true);
                        }
                        else
                        {
                            // log
                            Debug.LogError("房间状态异常： " + resp.Status.ToString());
                            // 回调
                            callback?.Invoke(false);
                        }
                    }
                    else
                    {
                        // log
                        Debug.LogError("通过大堂服务创建房间失败");
                        // 回调
                        callback?.Invoke(false);
                    }
                }));
            }
            else 
                // 回调
                callback?.Invoke(false);
        }
        
        /// <summary>
        /// 找到房间
        /// </summary>
        /// <param name="roomNamespace">房间名称空间</param>
        /// <param name="start">开始序号</param>
        /// <param name="count">数量</param>
        /// <param name="callback">回调</param>
        public void FindRoom(string roomNamespace, int start, int count, Action<List<LobbyRoom>> callback)
        {
            if (NetworkManager.Singleton && !NetworkManager.Singleton.IsListening)
            {
                StartCoroutine(LobbyService.AsyncListRoom(new ListRoomRequest()
                {
                    Namespace = roomNamespace,
                    Start = start,
                    Count = count,
                }, (resp) =>
                {
                    if (resp.Code == (uint)RelayCode.OK)
                    {
                        Debug.Log("列出房间成功");
                        // 回调
                        callback?.Invoke(resp.Items);
                    }
                    else
                    {
                        Debug.Log("按大厅服务列出房间故障");
                        // 回调
                        callback?.Invoke(null);
                    }
                }));
            }
            else 
                // 回调
                callback?.Invoke(null);
        }

        /// <summary>
        /// 加入房间
        /// </summary>
        /// <param name="roomUuid"></param>
        /// <param name="callback"></param>
        public void JoinRoom(string roomUuid, Action<bool> callback)
        {
            StartCoroutine(LobbyService.AsyncQueryRoom(roomUuid,
                ( _resp) =>
                {
                    if (_resp.Code == (uint)RelayCode.OK)
                    {
                        // log
                        Debug.Log("加入房间成功");
                        
                        // 设置房间数据
                        NetworkManager.Singleton.GetComponent<RelayTransportNetcode>()
                            .SetRoomData(_resp);
                        
                        // 回调
                        callback?.Invoke(true);
                    }
                    else
                    {
                        // log
                        Debug.LogError("加入房间失败");
                        
                        // 回调
                        callback?.Invoke(false);
                    }
                }));
        }

        /// <summary>
        /// 跟新玩家信息
        /// </summary>
        public void UpdatePlayer(string uid,string key,string value)
        {
            // 玩家信息
            var playerInfo = PlayerDic[uid];
            playerInfo.Properties[key] = value;
            // relay
            var relayTransport = NetworkManager.Singleton.GetComponent<RelayTransportNetcode>();
            relayTransport.UpdatePlayerInfo(playerInfo);
         
            // 事件 - 跟新房间信息事件
            SycnRalayUI.Instance.RefreshRoom(_joinRelayRoom,PlayerDic);
        }

        /// <summary>
        /// 跟新房间信息
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        public void UpdateRoom(string key,string value)
        {
            // relay
            var relayTransport = NetworkManager.Singleton.GetComponent<RelayTransportNetcode>();
            _joinRelayRoom.CustomProperties[key] = value;
            relayTransport.UpdateRoomCustomProperties(_joinRelayRoom.CustomProperties);
            
            // 事件 - 跟新房间信息事件
            SycnRalayUI.Instance.RefreshRoom(_joinRelayRoom,PlayerDic);
        }

        /// <summary>
        /// 快速加入房间
        /// </summary>
        public void QueryJoinRoom(string roomNamespace,Action<bool> callback)
        {
            if (NetworkManager.Singleton && !NetworkManager.Singleton.IsListening)
            {
                StartCoroutine(LobbyService.AsyncListRoom(new ListRoomRequest()
                {
                    Namespace = roomNamespace,
                    Start = 0,
                    Count = 10,
                }, ( resp) =>
                {
                    if (resp.Code == (uint)RelayCode.OK)
                    {
                        // log
                        Debug.Log("列出房间成功。");
                            
                        if (resp.Items.Count > 0)
                        {
                            foreach (var item in resp.Items)
                            {
                                if (item.Status == LobbyRoomStatus.Ready)
                                {
                                    // 加入房间
                                    JoinRoom(item.RoomUuid, callback);
                                }
                            }
                        }
                    }
                    else
                    {
                        // log
                        Debug.LogError("按大厅服务列出房间故障");
                        // 回调
                        callback?.Invoke(false);
                    }
                }));
            }
            else
            {
                // 回调
                callback?.Invoke(false);
            }
        }
        
        /// <summary>
        /// 在主机模式下处理启动网络代码 - 启用空闲的房间
        /// </summary>
        public void OnJoinAsHostButton(Action<bool> callback)
        {
            if (NetworkManager.Singleton && !NetworkManager.Singleton.IsListening)
            {
                FindRoom(RoomNameSpace,0,10,(list =>
                {
                    if (list != null && list.Count > 0)
                    {
                        foreach (var item in list)
                        {
                            if (item.Status == LobbyRoomStatus.Ready)
                            {
                                JoinRoom(item.RoomUuid, callback);
                                break;
                            }
                        }
                    }
                    else 
                        callback?.Invoke(false);
                }));
            }
            else 
                callback?.Invoke(false);
        }
        #endregion
        
    }
}